﻿using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;

namespace TestGame {
    /// <summary>
    /// Used by InputEntry to determine what different types of input should
    /// be accepted.
    /// </summary>
    enum AcceptableInput {
        All,
        Letters,
        Numbers
    }

    /// <summary>
    /// An inherited class from MenuEntry that is used to gather user input, 
    /// and display it to the user. Depending on what is used during the
    /// constructor, different input can be ignore, and special characters
    /// have their own flags.
    /// </summary>
    class InputEntry : MenuEntry {
        #region Fields

        /// <summary>
        /// The string that is displayed before the data.
        /// </summary>
        String prefix;
        
        /// <summary>
        /// The string that is manipulated by user input.
        /// </summary>
        String data;

        /// <summary>
        /// The type of input the entry should accept.
        /// </summary>
        AcceptableInput inputType;

        /// <summary>
        /// The maximum length of the user inputted string.
        /// </summary>
        int maxLength;

        /// <summary>
        /// Determines if periods should be allowed or not.
        /// </summary>
        bool periodEnabled;

        /// <summary>
        /// Determines if spaces should be allowed or not.
        /// </summary>
        bool spaceEnabled;

        /// <summary>
        /// A timer used for timing backspaces.
        /// </summary>
        TimeSpan backspaceTimer;

        /// <summary>
        /// Flag used to determine when to countdown on the backspace timer.
        /// </summary>
        bool backspaceFlag;

        /// <summary>
        /// The default max length
        /// </summary>
        const int DefaultMaxLength = 15;

        #endregion

        #region Properties

        /// <summary>
        /// The String that is manipulated by user input.
        /// </summary>
        public String Data {
            get { return data; }
            set { data = value; }
        }

        /// <summary>
        /// The maximum length of the user inputted string.
        /// </summary>
        public int MaxLength {
            get { return maxLength; }
            set { maxLength = value; }
        }

        /// <summary>
        /// Determines if periods should be allowed or not.
        /// </summary>
        public bool PeriodEnabled {
            get { return periodEnabled; }
            set { periodEnabled = value;  }
        }

        /// <summary>
        /// Determines if spaces should be allowed or not.
        /// </summary>
        public bool SpaceEnabled {
            get { return spaceEnabled; }
            set { spaceEnabled = value; }
        }

        #endregion

        #region Initialization

        /// <summary>
        /// Constructs an InputEntry given the model text and which type of 
        /// input the class should accept.
        /// </summary>
        /// <param name="text"></param>
        /// <param name="input"></param>
        public InputEntry(String text, AcceptableInput input) : base(text) {
            prefix = text;
            inputType = input;
            maxLength = DefaultMaxLength;

            data = "";
            backspaceTimer = TimeSpan.FromSeconds(0.5);

            backspaceFlag = false;
            periodEnabled = false;
            spaceEnabled = false;
        }

        /// <summary>
        /// Updates the MenuEntry's text based off the prefix and data.
        /// </summary>
        public void UpdateText() {
            if (data.Length >= maxLength) {
                data = data.Substring(0, maxLength);
            }

            this.Text = prefix + data;
        }

        #endregion

        #region Update Method

        /// <summary>
        /// Overridden method that is used to decrement the backspace timer 
        /// when necessary.
        /// </summary>
        /// <param name="screen"></param>
        /// <param name="isSelected"></param>
        /// <param name="gameTime"></param>
        public override void Update(GameScreen screen, bool isSelected, GameTime gameTime) {
            base.Update(screen, isSelected, gameTime);

            if (backspaceFlag) {
                backspaceFlag = false;
                backspaceTimer -= gameTime.ElapsedGameTime;
            }
        }

        #endregion

        #region Input Handling

        /// <summary>
        /// Handles any input for the MenuEntry. If any of the input matches
        /// for what the InputEntry is looking for, it is added to the data.
        /// </summary>
        /// <param name="input"></param>
        public override void HandleInput(InputState input) {
            CheckForPasteShortcut(input, ref data);
            CheckForBackSpace(input, ref data);
            CheckForSpecialCharacters(input, ref data);

            if (inputType == AcceptableInput.All || inputType == AcceptableInput.Numbers) {
                CheckForNumbers(input, ref data);
            }

            if (inputType == AcceptableInput.All || inputType == AcceptableInput.Letters) {
                CheckForLetters(input, ref data);
            }

            UpdateText(); 
        }

        #endregion

        #region Helper Methods

        /// <summary>
        /// Checks for the Key combination for paste (ctrl+v). If the
        /// combination is found, the pasted text is put into the given string.
        /// </summary>
        /// <param name="input"></param>
        /// <param name="str">The string to be changed if paste is detected</param>
        void CheckForPasteShortcut(InputState input, ref String str) {
            if (input.IsKeyPressed(Keys.LeftControl) || input.IsKeyPressed(Keys.RightControl)) {
                if (input.IsNewKeyPress(Keys.V)) {
                    str = CleanUp(Clipboard.GetText());
                }
            }
        }

        /// <summary>
        /// Cleans the given string, removing any spaces or newlines.
        /// </summary>
        /// <param name="str">The string to be cleaned.</param>
        /// <returns></returns>
        String CleanUp(String str) {
            char[] array = str.ToCharArray();
            String newStr = "";

            foreach (char c in array) {
                if (c != ' ' && c != '\n') {
                    newStr = newStr + c;
                }
            }

            return newStr;
        }

        /// <summary>
        /// Checks to see if the backspace key is pressed. If the key is
        /// pressed, the last character in the string is removed.
        /// </summary>
        /// <param name="input"></param>
        /// <param name="str"></param>
        void CheckForBackSpace(InputState input, ref String str) {
            if (str.Length > 0) {
                if (input.IsNewKeyPress(Keys.Back)) {
                    str = str.Remove(str.Length - 1);
                    backspaceTimer = TimeSpan.FromSeconds(0.5);
                }
                else if (input.IsKeyPressed(Keys.Back)) {
                    if (backspaceTimer <= TimeSpan.Zero) {
                        str = str.Remove(str.Length - 1);
                        backspaceTimer = TimeSpan.FromSeconds(0.2);
                    }
                    else {
                        backspaceFlag = true;
                    }
                }
            }
            
        }

        /// <summary>
        /// Given the InputState, the Method will check for any of the special
        /// characters that have thier own flag. If a any key press is found, 
        /// it will append the character to the string.
        /// </summary>
        void CheckForSpecialCharacters(InputState input, ref String str) {
            KeyboardState current = input.CurrentKeyboardStates[0];
            KeyboardState last = input.LastKeyboardStates[0];
            Keys[] lastPressed = last.GetPressedKeys();
            Keys[] currentPressed = current.GetPressedKeys();

            foreach (Keys pressedKey in currentPressed) {
                if (!lastPressed.Contains<Keys>(pressedKey)) {
                    switch (pressedKey) {
                        case Keys.OemPeriod:
                            if (periodEnabled) {
                                str += '.';
                            }
                            break;
                        case Keys.Space:
                            if (spaceEnabled) {
                                str += ' ';
                            }
                            break;
                    }
                }
            }
        }

        /// <summary>
        /// Given the InputState, the Method will check for the different 
        /// number key presses, and if a number key press is found, it will
        /// append the number to the string.
        /// </summary>
        /// <param name="input"></param>
        /// <param name="str"></param>
        /// <returns></returns>
        void CheckForNumbers(InputState input, ref String str) {
            KeyboardState current = input.CurrentKeyboardStates[0];
            KeyboardState last = input.LastKeyboardStates[0];
            Keys[] lastPressed = last.GetPressedKeys();
            Keys[] currentPressed = current.GetPressedKeys();

            foreach (Keys pressedKey in currentPressed) {
                if (!lastPressed.Contains<Keys>(pressedKey)) {
                    int castValue = (int)pressedKey;
                    int max = (int)Keys.D9;
                    int min = (int)Keys.D0;

                    if (castValue >= min && castValue <= max) {
                        int diff = (castValue - min);

                        str += diff.ToString();
                    }

                    min = (int)Keys.NumPad0;
                    max = (int)Keys.NumPad9;

                    if (castValue >= min && castValue <= max) {
                        int diff = (castValue - min);

                        str += diff.ToString();
                    }
                }
            }
        }

        /// <summary>
        /// Given the InputState, the Method will check for the different 
        /// letter key presses, and if a letter key press is found, it will
        /// append the letter to the string.
        /// </summary>
        /// <param name="input"></param>
        /// <param name="str"></param>
        /// <returns></returns>
        void CheckForLetters(InputState input, ref String str) {
            KeyboardState current = input.CurrentKeyboardStates[0];
            KeyboardState last = input.LastKeyboardStates[0];
            Keys[] lastPressed = last.GetPressedKeys();
            Keys[] currentPressed = current.GetPressedKeys();

            foreach (Keys pressedKey in currentPressed) {
                if (!lastPressed.Contains<Keys>(pressedKey)) {
                    int castValue = (int)pressedKey;
                    int max = (int)Keys.Z;
                    int min = (int)Keys.A;

                    if (castValue >= min && castValue <= max) {
                        char c = (char)((castValue - min) + 'A');

                        str += c;
                    }
                }
            }
        }

        #endregion
    }
}
